package com.nulldev.util.web.HttpServer3.extensions.websocket.frame;

import java.io.IOException;
import java.io.OutputStream;

import com.nulldev.util.VariableAPI.ByteUtil;
import com.nulldev.util.web.HttpServer3.extensions.websocket.exceptions.InvalidFrameException;

public class BasicFrameWriter implements FrameWriter {
	private final OutputStream out;

	public BasicFrameWriter(final OutputStream out) {
		this.out = out;
	}

	@SuppressWarnings("unused")
	@Override
	public void write(Frame frame) throws IOException {
		validate(frame);
		byte firstByte = ByteUtil.wsToBytes(new boolean[]
			{ frame.isFinalSegment(), frame.isRSV1(), frame.isRSV2(), frame.isRSV3(), false, false, false, false })[0];
		firstByte |= frame.getFrameType().getByte();
		byte secondByte = frame.getPayloadLength();
		byte[] extendedPayLoad = null;
		byte[] maskingKey = null;

		if (frame.isMasked()) {
			// need to merge with payload length
			secondByte |= ((byte) 0b10000000);
		}

		// write out first two byte
		out.write(firstByte);
		out.write(secondByte);

		int payloadSegment = Byte.toUnsignedInt(frame.getPayloadLength());
		if (payloadSegment == 126) {
			boolean[] bits = ByteUtil.wsToBitArray(frame.getPayload().length, 16);
			out.write(ByteUtil.wsToBytes(bits));
		} else if (payloadSegment == 127) {
			// 8 byte
			// no need to check bigger size frame since 4 byte int will always less than 8
			// byte length
			boolean[] bits = ByteUtil.wsToBitArray(frame.getPayload().length, 8 * 8);
			out.write(ByteUtil.wsToBytes(bits));
		}

		// write out masking key
		if (frame.isMasked()) {
			out.write(frame.getMaskingKey());
		}
		if (frame.getPayloadLength() > 0)
			out.write(frame.getPayload());

		out.flush();
	}

	@Override
	public void close() throws IOException {
		out.close();
	}

	public void validate(Frame frame) {
		if ((frame.isMasked() && frame.getMaskingKey() == null) || (frame.getMaskingKey() != null && frame.getMaskingKey().length != 4)
				|| (Byte.toUnsignedInt(frame.getPayloadLength()) == 126 && frame.getPayload().length > 0b1111111111111111)
				|| (Byte.toUnsignedInt(frame.getPayloadLength()) > 127 || Byte.toUnsignedInt(frame.getPayloadLength()) < 0))
			throw new InvalidFrameException();
	}
}